Skip to content

ROS2 Action 通信机制:分层架构、五路通道与状态机 ​

ROS2 的 Action 提供「提交目标、过程反馈、最终结果」的长任务交互模式。在接口层面它只是一组客户端/服务端 API,但底层并非一条专用通道,而是由多个 Service 和 Topic 组合而成。本文基于 rclcpp_action 与 rcl_action 源码(rolling 分支)分析 Action 的完整实现:分层架构、五路通信通道的 QoS 设计、Goal 状态机、线程安全与 Executor 集成。适合已经在使用 Action 接口、想理解底层机制或排查 Action 通信问题的读者。

分层架构 ​

Action 的实现自上而下分为四层,每层职责严格分离:

层级 职责 关键类型
L4 用户层 类型安全、异步 API、用户回调 Server<ActionT>, Client<ActionT>
L3 基类层 类型擦除、Executor 集成、事件分发 ServerBase, ClientBase
L2 C 层 协议逻辑、状态机、生命周期管理 rcl_action_server_t, rcl_action_client_t
L1 传输层 实际网络通信、序列化 RMW 实现(CycloneDDS 等)

五路通信通道 ​

Action 底层由 3 个 Service + 2 个 Topic 共 5 路通道组成:

通道 类型 Topic 名称 可靠性 持久性 历史深度 设计意图
Goal Service Service /{name}/_action/send_goal RELIABLE VOLATILE KEEP_LAST 10 目标提交不能丢失
Cancel Service Service /{name}/_action/cancel_goal RELIABLE VOLATILE KEEP_LAST 10 取消请求必须送达
Result Service Service /{name}/_action/get_result RELIABLE VOLATILE KEEP_LAST 10 结果查询必须可靠
Feedback Topic Topic /{name}/_action/feedback RELIABLE(默认) VOLATILE KEEP_LAST 10 过程数据可容忍丢失,可改 BEST_EFFORT 换低延迟
Status Topic Topic /{name}/_action/status RELIABLE TRANSIENT_LOCAL KEEP_LAST 1 新订阅者能立即获取当前状态快照

表中 QoS 是 rcl_action_server_get_default_options() 的默认值:三个 Service 用 rmw_qos_profile_services_default,Feedback Topic 用 rmw_qos_profile_default(RELIABLE / VOLATILE / KEEP_LAST 10),Status Topic 用 rcl_action_qos_profile_status_default(RELIABLE / TRANSIENT_LOCAL / KEEP_LAST 1)。

需要注意 Feedback Topic 的默认可靠性是 RELIABLE 而不是 BEST_EFFORT——反馈是持续的过程数据,语义上允许丢帧,用户可以自行改成 BEST_EFFORT 换取低延迟,但这需要显式配置。Status Topic 的 TRANSIENT_LOCAL + 深度 1 是「锁存」语义:任意时刻新订阅的客户端都能立刻收到最近一次状态快照,不需要等待下一次状态变化。

Service 底层同样由 DDS 的两个 Topic(Request + Reply)实现,请求与响应的关联由 RMW 实现负责。

Feedback Topic 与 Status Topic 的区别 ​

这是最容易混淆的两个通道:

维度 Feedback Topic Status Topic
消息类型 ActionT::Impl::FeedbackMessage(含用户自定义字段) action_msgs::msg::GoalStatusArray(纯枚举)
覆盖范围 单个目标,消息中携带 goal_id 用于匹配 所有目标的状态快照列表
客户端处理 按 goal_id 找到对应 GoalHandle,触发用户的 feedback_callback 遍历列表,仅调用 goal_handle->set_status(),不触发任何用户回调
触发方 用户代码显式调用 publish_feedback() 框架在任意目标状态变化时自动调用 publish_status()
QoS RELIABLE(默认),可改 BEST_EFFORT 换低延迟 RELIABLE + TRANSIENT_LOCAL,可靠且可重放
语义 这个任务执行到了哪一步(业务进度) 系统中哪些目标处于什么状态(生命周期)
服务端触发 publish_status() 的时机(框架自动):
  - Goal 被 ACCEPT 后(execute_goal_request_received)
  - ACCEPT_AND_EXECUTE 时状态变为 EXECUTING 后
  - Cancel 请求被处理,至少一个目标状态变化后(execute_cancel_request_received)

服务端触发 publish_feedback() 的时机(用户手动):
  - 用户在 AcceptedCallback 的执行线程中调用 goal_handle->publish_feedback(fb)

通信时序 ​

正常执行流程 ​

取消目标流程 ​

延迟结果:Result 的 Push 机制 ​

客户端可以先请求结果,等结果就绪时由服务端主动推送——这是 Result Service 区别于普通请求-响应模式的关键设计:

多个客户端可以同时等待同一个目标的结果,服务端会向所有等待者统一推送。async_get_result() 在任务完成前调用也不会丢失:请求被暂存起来,结果就绪后逐个响应。

Goal 状态机 ​

状态转换图 ​

状态转换表 ​

当前状态 触发事件 目标状态 触发方
ACCEPTED GOAL_EVENT_EXECUTE EXECUTING ACCEPT_AND_EXECUTE 响应时框架自动触发
EXECUTING GOAL_EVENT_SUCCEED SUCCEEDED 用户调用 goal_handle->succeed()
EXECUTING GOAL_EVENT_ABORT ABORTED 用户调用 goal_handle->abort()
EXECUTING GOAL_EVENT_CANCEL_GOAL CANCELING rcl_action_process_cancel_request() 内部触发
CANCELING GOAL_EVENT_CANCELED CANCELED 用户调用 goal_handle->canceled()
CANCELING GOAL_EVENT_SUCCEED SUCCEEDED 任务已完成时忽略取消请求
CANCELING GOAL_EVENT_ABORT ABORTED 取消过程中发生错误

注意 CANCELING 是一个可以流向三种终态的中间状态:任务可能在取消信号到来前已经完成(SUCCEEDED),也可能在取消过程中出错(ABORTED)。

终端状态后的清理 ​

进入 SUCCEEDED / ABORTED / CANCELED 后:

  1. 结果被缓存到 goal_results_[uuid],供尚未查询的客户端读取
  2. 通过 rcl_action_notify_goal_done() 通知 rcl 层
  3. 超过 result_timeout 后,由定时器触发 execute_check_expired_goals() 从所有 map 中清除。默认值存在版本差异:Iron 及之后为 10 秒(RCUTILS_S_TO_NS(10)),Humble 及之前为 15 分钟;还可配置为 -1 永久保留、0 立即丢弃。跨版本部署或依赖「事后很久还能查结果」的场景要注意这个默认值

线程安全设计 ​

Server 侧 ​

cpp
class ServerBaseImpl {
  // 两把锁,固定加锁顺序防止死锁
  // 顺序:unordered_map_mutex_ → action_server_reentrant_mutex_

  std::recursive_mutex action_server_reentrant_mutex_;  // 保护所有 rcl_action API 调用
  std::recursive_mutex unordered_map_mutex_;            // 保护三张 map

  // 结果缓存:目标完成后存储,等待客户端拉取
  std::unordered_map<GoalUUID, std::shared_ptr<void>>              goal_results_;
  // 延迟结果等待队列:客户端先请求结果但结果未就绪时暂存请求头
  std::unordered_map<GoalUUID, std::vector<rmw_request_id_t>>      result_requests_;
  // rcl 句柄缓存:防止 rcl 内部存储释放后上层访问野指针
  std::unordered_map<GoalUUID, std::shared_ptr<rcl_action_goal_handle_t>> goal_handles_;

  // 原子标志:防止 execute() 对同一事件重入处理
  std::atomic<bool> goal_request_ready_;
  std::atomic<bool> cancel_request_ready_;
  std::atomic<bool> result_request_ready_;
  std::atomic<bool> goal_expired_;
};

关键设计是 compare_exchange_strong:即使 Executor 在多线程模式下,每个事件也只被处理一次:

cpp
bool expected = true;
if (!pimpl_->goal_request_ready_.compare_exchange_strong(expected, false)) {
  return;  // 已被其他线程处理,直接返回
}

Client 侧 ​

cpp
std::mutex goal_handles_mutex_;  // 保护 goal_handles_ map(弱引用 map)

// 结果通过 std::promise / std::shared_future 跨线程传递(天然线程安全)
// handle_feedback_message 检测 weak_ptr 悬空,自动清理过期句柄:
if (!goal_handle) {
  goal_handles_.erase(goal_id);  // 用户不再持有引用,自动清理
  return;
}

Executor 集成机制 ​

ServerBase 和 ClientBase 均继承自 rclcpp::Waitable,通过统一接口接入 Executor 的事件循环:

Action 对 Executor 完全透明:在 Executor 看来,它只是一个普通的 Waitable,无需任何特殊调度逻辑。add_to_wait_set() 负责将 Action 的所有子实体批量注册到 wait_set 中。

关键源码索引 ​

文件 内容
rclcpp_action/include/rclcpp_action/server.hpp ServerBase、Server<ActionT> 定义,三个用户回调类型声明
rclcpp_action/include/rclcpp_action/client.hpp ClientBase、Client<ActionT> 定义,SendGoalOptions 结构体
rclcpp_action/src/server.cpp ServerBaseImpl、五路事件处理、publish_status/feedback/result 实现
rclcpp_action/src/client.cpp ClientBaseImpl、handle_feedback_message/handle_status_message 实现
rclcpp_action/src/server_goal_handle.cpp ServerGoalHandle 生命周期管理,succeed/abort/canceled 实现
rcl/rcl_action/include/rcl_action/action_server.h rcl_action_server_options_t 默认配置,QoS 声明

整体类图 ​

要点 ​

  • Action = 3 个 Service(goal / cancel / result)+ 2 个 Topic(feedback / status),QoS 默认值出自 rcl_action_server_get_default_options()
  • Feedback 默认 RELIABLE;想允许丢帧换低延迟需要显式改成 BEST_EFFORT。Status 是 TRANSIENT_LOCAL + 深度 1 的锁存通道,新订阅者立即拿到最新快照,且不触发用户回调
  • Result Service 支持「先请求、后推送」:结果未就绪时暂存请求头,就绪后向所有等待者统一响应
  • Goal 状态机里 CANCELING 可流向 SUCCEEDED / CANCELED / ABORTED 三种终态;终端结果缓存时长默认 Iron 起 10 秒、Humble 及之前 15 分钟,跨版本部署时注意
  • Server / Client 对 Executor 只是普通 Waitable,事件去重靠原子标志的 compare_exchange_strong
最近更新

基于 VitePress 构建